#ifndef AVALON_SIGNALSINK_HPP
#define AVALON_SIGNALSINK_HPP
#include <deque>
#include <vector>
#include <boost/any.hpp>
#include <boost/noncopyable.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include "Avalon/Base/Functional.hpp"
#include "Avalon/SignalHandling/SignalHandling.hpp"

namespace Avalon {
namespace SignalHandling {

  /*! \class SignalSink
   *  \brief Stores signals and their parameters.
   */
  class SignalSink : private boost::noncopyable {
    public:

      /*! \struct SignalEntry
       *  \brief Stores the contents of a signal.
       */
      struct SignalEntry {

        //! The type of signal.
        const std::type_info* m_type;

        //! Stores the signal's parameters.
        std::vector<boost::any> m_parameters;

        //! Constructs a SignalEntry.
        /*!
          \param type The type of signal.
          \param parameters The tuple containing the signal's parameters.
        */
        SignalEntry(const std::type_info& type,
          const std::vector<boost::any>& parameters);
      };

      //! Constructs a SignalSink.
      SignalSink();

      ~SignalSink();

      //! Returns the next signal.
      /*!
        \param timeout The amount of time to wait for the next signal.
        \return The next signal queued.
      */
      SignalEntry GetNextSignal(boost::posix_time::time_duration timeout);

      //! Returns a slot compatible with this signal handler.
      /*!
        \tparam SlotType A function type of the form R (P0, P1, ...)
        \param slot The slot to make compatible with this signal handler.
        \return A slot compatible with this signal handler.
      */
      template<typename SlotType>
      const typename GetFunctionType<SlotType>::Type& GetSlot(
          const SlotType& slot) {
        return GetSlotImplementation<SlotType>::Invoke(this, slot);
      }

    private:
      boost::mutex m_mutex;
      boost::condition_variable m_signalEmptyCondition;
      std::deque<SignalEntry> m_signalEntries;

      template<typename SlotType, int Arity = Signal::arity>
      struct GetSlotImplementation {};

      #define PUSH_BACK_PARAMETERS(z, n, a)                                    \
        parameters.push_back(a ## n);

      #define SIGNAL_PARAMETER_TYPES(z, n, q)                                  \
        BOOST_PP_COMMA_IF(n) typename                                          \
        boost::function_traits<SlotType>::arg##n_type

      #define PARAMETER_TYPES(z, n, a)                                         \
        BOOST_PP_COMMA_IF(n) typename boost::remove_reference<a##n>::type

      #define CALLBACK_PLACEHOLDERS(z, n, a)                                   \
        BOOST_PP_COMMA_IF(n) boost::arg<n + 1>()

      #define GET_TUPLE_ELEMENTS(z, n, a)                                      \
        BOOST_PP_COMMA_IF(n) boost::get<n>(a)

      #define BOOST_PP_LOCAL_MACRO(n)                                          \
      template<typename SlotType>                                              \
      struct GetSlotImplementation<SlotType, n> {                              \
        static typename GetFunctionType<SlotType>::Type Invoke(                \
            SignalSink* sink) {                                                \
          return boost::bind(&SignalSink::Slot BOOST_PP_IF(n, <,)              \
            BOOST_PP_REPEAT(n, SIGNAL_PARAMETER_TYPES,) BOOST_PP_IF(n, >,),    \
            sink BOOST_PP_COMMA_IF(n) BOOST_PP_REPEAT(n,                       \
            CALLBACK_PLACEHOLDERS,));                                          \
        }                                                                      \
      };                                                                       \
                                                                               \
      BOOST_PP_IF(n, (template<BOOST_PP_ENUM_PARAMS(n, typename A)>),)         \
      void Slot(BOOST_PP_ENUM_BINARY_PARAMS(n, A, a)) {                        \
        boost::lock_guard<boost::mutex> lock(m_mutex);                         \
        std::vector<boost::any> parameters;                                    \
        BOOST_PP_REPEAT(n, PUSH_BACK_PARAMETERS, a);                           \
        m_signalEntries.push_back(SignalEntry(typeid(boost::function<          \
          void (BOOST_PP_ENUM_PARAMS(n, A))>), parameters));                   \
        if(m_signalEntries.size() == 1) {                                      \
          m_signalEmptyCondition.notify_all();                                 \
        }                                                                      \
      }

      #define BOOST_PP_LOCAL_LIMITS (0, AVALON_SIGNAL_HANDLING_PARAMETER_COUNT)
      #include BOOST_PP_LOCAL_ITERATE()
      #undef PUSH_BACK_PARAMETERS
      #undef SIGNAL_PARAMETER_TYPES
      #undef PARAMETER_TYPES
      #undef CALLBACK_PLACEHOLDERS
      #undef GET_TUPLE_ELEMENTS
  };
}
}

#endif // AVALON_SIGNALSINK_HPP
